﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
using FizzWare.NBuilder;
using FizzWare.NBuilder.Dates;
using NHDay_Linq.Domain;
using NHDay_Linq.Dto;
using NHDay_Linq.Helper;
using NHibernate;
using NHibernate.Linq;
using NUnit.Framework;

namespace NHDay_Linq
{
    [TestFixture]
    public class R01_LinqBaseTests : TestBase
    {

        [TestFixtureSetUp]
        public void TestFixtureSetUp()
        {
            //HibernatingRhinos.Profiler.Appender.NHibernate.NHibernateProfiler.Initialize();
        }

        [TestFixtureTearDown]
        public void TestFixtureTearDown()
        {
            //HibernatingRhinos.Profiler.Appender.NHibernate.NHibernateProfiler.Stop();
        }

        [Test]
        public void Parse_Expression_Tree_Is_Complex()
        {
            using (ISession session = SessionFactory.OpenSession())
            using (ITransaction tx = session.BeginTransaction())
            {
                DateTime start = The.Year(2010).On.April.The1st;
                DateTimeGenerator generator = new DateTimeGenerator(start);

                CreateTenAdults(session, generator);

                DateTime middle = start.AddDays(5);

                //This works
                var query = from p in session.Query<Person>()
                            where p.BirthDate > middle
                            select p;
                Assert.That(query.Count(), Is.EqualTo(5));

                //this does not work.. because of the cast to Person.
                var querynotWorking = from Person p in session.Query<Person>()
                                      where p.BirthDate > middle
                                      select p;
                Assert.That(querynotWorking.Count(), Is.EqualTo(5));
            }
        }

        [Test]
        public void Deferred_and_not_deferred_operators()
        {
            List<Int32> source = Enumerable.Range(1, 10).ToList();
            var query1 = from Int32 num in source
                         where num % 2 == 0
                         select num;
            Console.WriteLine("\nWriting first enumeration");
            foreach (var num in query1)
            {
                Console.WriteLine(num);
            }

            source.AddRange(Enumerable.Range(11, 10));
            Console.WriteLine("\nWriting second enumeration");
            foreach (var num in query1)
            {
                Console.WriteLine(num);
            }
        }

        [Test]
        public void Deferred_in_linq2nh_queries()
        {
            using (ISession session = SessionFactory.OpenSession())
            using (ITransaction tx = session.BeginTransaction())
            {
                DateTime start = The.Year(2010).On.April.The1st;
                DateTimeGenerator generator = new DateTimeGenerator(start);

                CreateTenAdults(session, generator);

                DateTime middle = start.AddDays(5);
                var query = session.Query<Person>()
                    .Where(p => p.BirthDate > middle);
              
                Assert.That(query.Count(), Is.EqualTo(5));
              
                //Save another 10 person with birthdate > middle
                CreateTenAdults(session, generator);
                session.Flush();

                Assert.That(query.Count(), Is.EqualTo(15));
                Assert.That(query.Count(), Is.EqualTo(15));
            }
        }

        [Test]
        public void Deferred_in_linq2nh_queries_common_errors()
        {
            using (ISession session = SessionFactory.OpenSession())
            using (ITransaction tx = session.BeginTransaction())
            {
                DateTime start = The.Year(2010).On.April.The1st;
                DateTimeGenerator generator = new DateTimeGenerator(start);

                CreateTenAdults(session, generator);

                DateTime middle = start.AddDays(5);
                var query = session.Query<Person>()
                    .Where(p => p.BirthDate > middle);

                foreach (var person in query)
                {
                    Console.WriteLine(person.FirstName);
                }

                //do something other

                //IMPORTANT: This executes again the query
                foreach (var person in query)
                {
                    Console.WriteLine(person.FirstName);
                }
            }
        }

        [Test]
        public void Deferred_in_linq2nh_queries_use_not_deferred_to_avoid_multiple_queries()
        {
            using (ISession session = SessionFactory.OpenSession())
            using (ITransaction tx = session.BeginTransaction())
            {
                DateTime start = The.Year(2010).On.April.The1st;
                DateTimeGenerator generator = new DateTimeGenerator(start);

                CreateTenAdults(session, generator);

                DateTime middle = start.AddDays(5);
                var result = session.Query<Person>()
                    .Where(p => p.BirthDate > middle)
                    .ToList();

                foreach (var person in result)
                {
                    Console.WriteLine(person.FirstName);
                }

                //do something other
                Console.WriteLine("Something other");

                //IMPORTANT: This does not execute again the query;
                foreach (var person in result)
                {
                    Console.WriteLine(person.FirstName);
                }
            }
        }

        [Test]
        public void IQueryable_is_flexible()
        {
            using (ISession session = SessionFactory.OpenSession())
            using (ITransaction tx = session.BeginTransaction())
            {
                DateTime start = The.Year(2010).On.April.The1st;
                DateTimeGenerator generator = new DateTimeGenerator(start);

                CreateTenAdults(session, generator);
                //create a query with a condition
                DateTime middle = start.AddDays(5);
                var query1 = session.Query<Person>()
                    .Where(p => p.BirthDate > middle);

                //this will issue a select to access the element
                foreach (var person in query1)
                {
                    Console.WriteLine(person.FirstName);
                }

                //do something other
                Console.WriteLine("getting count");
                //if you call count you get a Count executed on the database
                Console.WriteLine("Record N°:" + query1.Count());

                //You can paginate
                foreach (var person in query1.Skip(2).Take(2))
                {
                    Console.WriteLine(person.FirstName);
                }

                //etc etc....
            }
        }

        [Test]
        public void Programmatically_manipulation_of_a_query()
        {
            using (ISession session = SessionFactory.OpenSession())
            using (ITransaction tx = session.BeginTransaction())
            {
                DateTime start = The.Year(2010).On.April.The1st;
                DateTimeGenerator generator = new DateTimeGenerator(start);

                CreateTenAdults(session, generator);

                DateTime middle = start.AddDays(5);
                var query1 = session.Query<Person>()
                    .Where(p => p.BirthDate > middle);

                foreach (var person in query1)
                {
                    Console.WriteLine(person.FirstName);
                }

                //do something other
                Console.WriteLine("modifying the query");
                var query2 = query1.Where(p => p.BirthDate < middle.AddDays(2));

                //new query is a composition of the two
                foreach (var person in query2)
                {
                    Console.WriteLine(person.FirstName);
                }

                Console.WriteLine("executing original again");
                //The original query is not changed you can execute again
                foreach (var person in query1)
                {
                    Console.WriteLine(person.FirstName);
                }
            }
        }

        [Test]
        public void Projection_with_HQL()
        {
            using (ISession session = SessionFactory.OpenSession())
            using (ITransaction tx = session.BeginTransaction())
            {
                DateTime start = The.Year(2010).On.April.The1st;
                DateTimeGenerator generator = new DateTimeGenerator(start);

                CreateTenAdults(session, generator);
                
                DateTime middle = start.AddDays(5);
                //it fails, it needs to import the dto class into nhibernate
                var hqlResult = session.CreateQuery(@"
                    select new AdultDtoSample(P.Id, P.BirthDate)
                    from Person P
                    where P.BirthDate > :dateParam")
                   .SetDateTime("dateParam", middle)
                   .List<AdultDtoSample>();

                foreach (var adultDtoSample in hqlResult)
                {
                    Console.WriteLine("Id: {0}\tBirthDate:{1}", adultDtoSample.Id, adultDtoSample.BirthDate);
                }
            }
        }

        [Test]
        public void Projection_with_LINQ()
        {   
            using (ISession session = SessionFactory.OpenSession())
            using (ITransaction tx = session.BeginTransaction())
            {
                DateTime start = The.Year(2010).On.April.The1st;
                DateTimeGenerator generator = new DateTimeGenerator(start);

                CreateTenAdults(session, generator);

                DateTime middle = start.AddDays(5);
                //nhibernate does not need to know anything about AdultDtoSample class
                var linqResult = 
                    from p in session.Query<Person>()
                    where p.BirthDate > middle
                    select new AdultDtoSample(p.Id, p.BirthDate);

                foreach (var adultDtoSample in linqResult)
                {
                    Console.WriteLine("Id: {0}\tBirthDate:{1}", adultDtoSample.Id, adultDtoSample.BirthDate);
                }
            }
        }

        [Test]
        public void Multiple_projection_with_LINQ()
        {
            using (ISession session = SessionFactory.OpenSession())
            using (ITransaction tx = session.BeginTransaction())
            {
                DateTime start = The.Year(2010).On.April.The1st;
                DateTimeGenerator generator = new DateTimeGenerator(start);

                CreateTenAdults(session, generator);

                DateTime middle = start.AddDays(5);
                
                var linqResult =
                    from p in session.Query<Person>()
                    where p.BirthDate > middle
                    select p;

                var projected1 = linqResult
                    .Select(p => new AdultDtoSample(p.Id, p.BirthDate));
                foreach (var adultDtoSample in projected1)
                {
                    Console.WriteLine("Id: {0}\tBirthDate:{1}", adultDtoSample.Id, adultDtoSample.BirthDate);
                }

                Console.WriteLine("Now do a projection again");
                
                var projected2 = linqResult
                    .Select(p => new SimpleAdultDto(p.Id, p.BirthDate, p.FirstName));
                     foreach (var adultDtoSample in projected2)
                {
                    Console.WriteLine("Id: {0}\tBirthDate:{1}\tFirstName:{2}",
                        adultDtoSample.Id, adultDtoSample.BirthDate, adultDtoSample.FirstName);
                }

            }
        }

        [Test]
        public void Projection_with_Dto_Generator()
        {
             using (ISession session = SessionFactory.OpenSession())
            using (ITransaction tx = session.BeginTransaction())
            {
                DateTime start = The.Year(2010).On.April.The1st;
                DateTimeGenerator generator = new DateTimeGenerator(start);

                CreateTenAdults(session, generator);

                DateTime middle = start.AddDays(5);
                
                var linqResult = 
                   session.Query<Adult>()
                    .Where(p => p.BirthDate > middle)
                    .Select(AdultDtoSample2.Assembler.ExpressionSelector);

                linqResult.ForEach(DumpObjectInXml);
            }
        }

        [Test]
        public void Projection_with_anonymous_types()
        {
            using (ISession session = SessionFactory.OpenSession())
            using (ITransaction tx = session.BeginTransaction())
            {
                DateTime start = The.Year(2010).On.April.The1st;
                DateTimeGenerator generator = new DateTimeGenerator(start);

                CreateTenAdults(session, generator);

                DateTime middle = start.AddDays(5);
               
                var linqResult =
                    from p in session.Query<Person>()
                    where p.BirthDate > middle
                    select new
                               {
                                   Id = p.Id,
                                   Name = p.FirstName,
                                   BirthDate = p.BirthDate,
                               };

                foreach (var dto in linqResult)
                {
                    Console.WriteLine("Id={0}\tName={1}\tBirthDate={2}",
                        dto.Id, dto.Name, dto.BirthDate);
                }
            }
        }
        
        [Test]
        public void Projection_with_complex_anonymous_types()
        {
            using (ISession session = SessionFactory.OpenSession())
            using (ITransaction tx = session.BeginTransaction())
            {
                DateTime start = The.Year(2010).On.April.The1st;
                DateTimeGenerator generator = new DateTimeGenerator(start);

                CreateTenAdults(session, generator);

                DateTime middle = start.AddDays(5);
                
                var linqResult =
                    from p in session.Query<Person>()
                    where p.BirthDate > middle
                    select new
                    {
                        Id = p.Id,
                        Name = p.FirstName + " " + p.LastName,
                        Age = DateTime.Now.Year - p.BirthDate.Year,
                    };

                foreach (var dto in linqResult)
                {
                    Console.WriteLine("Id={0}\tName={1}\tBirthDate={2}",
                        dto.Id, dto.Name, dto.Age);
                }
            }
        }

        /// <summary>
        /// Creates the ten adults.
        /// </summary>
        /// <param name="session">The session.</param>
        /// <param name="generator">The generator.</param>
        private void CreateTenAdults(ISession session, DateTimeGenerator generator)
        {
            var persons = Builder<Adult>.CreateListOfSize(10)
                .WhereAll().Have(x => x.Id = 0)
                .WhereAll().Have(x => x.BirthDate = generator.NextDay())
                .Build();
            foreach (var person in persons)
            {
                session.Save(person);
            }
            session.Flush();
        }

        public void DumpObjectInXml(Object obj)
        {
            XmlSerializer ser = new XmlSerializer(obj.GetType(), "");
            using (StringWriter sw = new StringWriter())
            {
                XmlWriterSettings settings = new XmlWriterSettings();
                settings.OmitXmlDeclaration = true;
                settings.Indent = true;
                settings.NewLineOnAttributes = true;
                XmlSerializerNamespaces blank = new XmlSerializerNamespaces();
                blank.Add("", "");
                using (XmlWriter writer = XmlWriter.Create(sw, settings))
                {
                    ser.Serialize(writer, obj, blank);
                }
                Console.WriteLine(sw.ToString());
            }
        }
    }
}
